Repository navigation
feat(bucketpolicy): add the bucket policy and its evaluation - #60
Conversation
d4024b1 to
f797e0f
Compare
f797e0f to
75af1ea
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Exclude * from queried principals in Effective and add the corresponding test.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds a pure bucket-policy package for validation, canonical ETags, permission evaluation, and change detection.
Changes:
- Defines policy documents, statements, effects, and validation rules.
- Implements canonical JSON, SHA-256 ETags, effective permissions, and change tracking.
- Adds table-driven tests.
File summaries
| File | Summary |
|---|---|
pkg/policy/policy.go |
Implements policy validation, canonicalization, evaluation, and change detection. Moderate issue (2 votes): Effective treats * as a queryable principal. |
pkg/policy/policy_test.go |
Tests policy validation, canonicalization, ETags, evaluation, and change detection. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
75af1ea to
8c144d5
Compare
2821817 to
7ecf429
Compare
7ecf429 to
1133cac
Compare
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1133cac89e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
1133cac to
6c425fc
Compare
a981176 to
fac3ea6
Compare
fac3ea6 to
6dc5b91
Compare
362147b to
dfdada0
Compare
660aaa0 to
b063707
Compare
a7fdfdf to
7edd92e
Compare
| if err != nil { | ||
| panic(err) // sha2-256 with the default length cannot fail | ||
| } | ||
| return `"` + cid.NewCidV1(cid.DagCBOR, sum).String() + `"` |
There was a problem hiding this comment.
Yeah... I think if you defined the policy types something like I suggested fil-one/RFC#30 (comment) you could use dag-json-gen to deterministically encode the policy.
Then you lose the normalize, sortedSet, strictKeys and encodeCBOR functions since you get all that for free just by using the format.
So you'd Sum over policy.MarshalDagJson() here and use the DAG JSON IPLD codec in the CID.
I don't think this has to block the PR but would have to go in before prod if we do do it.
d2a610d to
7d8a79a
Compare
Adds pkg/bucketpolicy, a pure package with no store or transport dependencies. Validate applies the RFC's 422 rules: at least one statement, a recognized effect, non-empty principals and actions, actions from the policy vocabulary (s3perm.PolicyAction), and named principals that exist for the tenant. Canonical produces compact JSON in a fixed field order with input order kept, and ETag is the quoted hex SHA-256 of that encoding, so stored tags compare with If-Match values. Effective computes a principal's actions on a bucket: the union of Allow statements naming it or the wildcard, minus the union of Deny statements naming it or the wildcard. Named lists the principals a policy names and whether it uses the wildcard. Changed lists the principals whose effective set differs between two policies, expanding the wildcard to the tenant's principals, which is the set a policy write must invalidate. The wildcard is not itself a principal: Effective is nil for "*", so only Changed expands it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A principal list naming an id twice decoded as given and was stored that way. Decoding now keeps the first occurrence of each id, so every write path that goes through Decode, the policy API and the CreateBucket header alike, stores and tags the filtered document. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…r CID The statement, principal and action lists are sets: Decode and Canonical deduplicate and sort them, and Hilt stores and returns the sorted document. The ETag is the CID (v1, dag-cbor, sha2-256) of the DAG-CBOR encoding of that document, so two documents that differ only in order or in duplicates carry the same tag. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
encoding/json decodes "sid": null into an empty string without error, so the policy was accepted and stored without its sid. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bucket policy JSON uses the AWS names Statement, Sid, Effect, Principal and Action, and the effects Allow and Deny (RFC 30, 113d4ded). The strict key check keeps refusing any other spelling, so the lowercase names are now unknown fields, and Version and Resource stay refused. The DAG-CBOR key order is unchanged (length, then bytes), so the ETags change only through the renamed keys and effects. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Policy.UnmarshalJSON runs the strict decode, the structural checks and the normalization, so a policy that is not well formed never comes into existence: not from Decode, and not from a store reading a document back with encoding/json. Validate keeps the principal-existence check and repeats the structural one for documents built in code, which the service's write path still passes through it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The ETag is the CID (CIDv1, dag-json, sha2-256) of the policy encoded as
sets, following the schema from the IAM RFC: each statement's principal
and action lists are maps from member to {}, the wildcard principal is
the set {"*"}, and the policy is the set of its statements' CIDs. The
encoder is generated with dag-json-gen (pkg/bucketpolicy/internal/dagjson,
go generate), which writes map keys and struct fields sorted, so the
order the caller wrote things in and any duplicate cannot reach the
bytes. The canonical AWS-shaped document is derived from the same form:
statements ordered by CID, principals and actions sorted. That replaces
the hand-built DAG-CBOR encoding and the sort by JSON encoding.
The encoded types live in their own import-free package because the
generator builds with -tags codegen, which excludes libforge's generated
files and so anything that imports s3perm. The generator's map limit
becomes a parse-time bound of 4096 statements and principals per
statement, so a document that parses always encodes.
strictKeys stays: the request body is the AWS array shape with a
Principal union, which the generator cannot express, and encoding/json
matches names case-insensitively.
The two pinned ETags are re-pinned from hand-assembled DAG-JSON bytes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
7d8a79a to
b4c7f06
Compare
Sets every staging pin in `terraform/envs/staging/apps/terraform.tfvars` to what `terraform/envs/dev/apps/terraform.tfvars` pins at ccf4093. Merging this is the promotion: Check and deploy applies `terraform/envs/staging/apps` on the merge commit, with no further confirmation. dev passed its smoke test on that commit in https://github.com/fil-forge/infra-central/actions/runs/38577102150. This pull request is rebuilt each time dev passes its smoke test after a push to `main`, and closed once staging matches dev. To hold a service back, push to `bot/promote-staging` by hand: the bot leaves the branch alone while this pull request is open. ## hilt `sha256:80076f3` → `sha256:5b6d022` What fil-forge/hilt brings over staging's pin, `a026d16..42dd070`: - feat(s3perm): add the policy vocabulary (fil-forge/hilt#58) (89d4ccf) - feat(store): add the principal store and share-locked reads (fil-forge/hilt#59) (434839e) - feat(bucketpolicy): add the bucket policy and its evaluation (fil-forge/hilt#60) (b30465e) - feat(store): add the bucket policy store with compare-and-set writes (fil-forge/hilt#61) (1ff52cb) - fix(store): bound every Postgres lock wait (fil-forge/hilt#84) (98d7a12) - feat(api): bind access keys to principals (fil-forge/hilt#64) (894624d) - feat(grant): rotate and revoke principal-bound key delegations (fil-forge/hilt#65) (e372bcd) - feat(api): add principals and principal-bound access keys (fil-forge/hilt#66) (880f780) - feat(api): add bucket policies and the principal access reads (fil-forge/hilt#67) (f366100) - feat(rpc): authorize principal-bound keys against bucket policies (fil-forge/hilt#68) (9e93a34) - feat(rpc): answer /s3/bucket/info for a principal-bound key (fil-forge/hilt#85) (b7ed334) - test(itest): cover the IAM model end to end and document it (fil-forge/hilt#69) (fa1512f) - feat(api): revoke every key's delegations before removing a tenant (fil-forge/hilt#80) (70f9380) - feat(rpc): read and write bucket policies over /s3/bucket/policy (fil-forge/hilt#89) (6a675f4) - feat(bucketpolicy): make the bucket policy an AWS bucket policy document (fil-forge/hilt#96) (42dd070)
Sets every staging pin in `terraform/envs/staging/apps/terraform.tfvars` to what `terraform/envs/dev/apps/terraform.tfvars` pins at 8f6b78d. Merging this is the promotion: Check and deploy applies `terraform/envs/staging/apps` on the merge commit, with no further confirmation. dev passed its smoke test on that commit in https://github.com/fil-forge/infra-central/actions/runs/38581685131. This pull request is rebuilt each time dev passes its smoke test after a push to `main`, and closed once staging matches dev. To hold a service back, push to `bot/promote-staging` by hand: the bot leaves the branch alone while this pull request is open. ## hilt `sha256:80076f3` → `sha256:5b6d022` What fil-forge/hilt brings over staging's pin, `a026d16..42dd070`: - feat(s3perm): add the policy vocabulary (fil-forge/hilt#58) (89d4ccf) - feat(store): add the principal store and share-locked reads (fil-forge/hilt#59) (434839e) - feat(bucketpolicy): add the bucket policy and its evaluation (fil-forge/hilt#60) (b30465e) - feat(store): add the bucket policy store with compare-and-set writes (fil-forge/hilt#61) (1ff52cb) - fix(store): bound every Postgres lock wait (fil-forge/hilt#84) (98d7a12) - feat(api): bind access keys to principals (fil-forge/hilt#64) (894624d) - feat(grant): rotate and revoke principal-bound key delegations (fil-forge/hilt#65) (e372bcd) - feat(api): add principals and principal-bound access keys (fil-forge/hilt#66) (880f780) - feat(api): add bucket policies and the principal access reads (fil-forge/hilt#67) (f366100) - feat(rpc): authorize principal-bound keys against bucket policies (fil-forge/hilt#68) (9e93a34) - feat(rpc): answer /s3/bucket/info for a principal-bound key (fil-forge/hilt#85) (b7ed334) - test(itest): cover the IAM model end to end and document it (fil-forge/hilt#69) (fa1512f) - feat(api): revoke every key's delegations before removing a tenant (fil-forge/hilt#80) (70f9380) - feat(rpc): read and write bucket policies over /s3/bucket/policy (fil-forge/hilt#89) (6a675f4) - feat(bucketpolicy): make the bucket policy an AWS bucket policy document (fil-forge/hilt#96) (42dd070)
Sets every staging pin in `terraform/envs/staging/apps/terraform.tfvars` to what `terraform/envs/dev/apps/terraform.tfvars` pins at d236fcd. Merging this is the promotion: Check and deploy applies `terraform/envs/staging/apps` on the merge commit, with no further confirmation. dev passed its smoke test on that commit in https://github.com/fil-forge/infra-central/actions/runs/38583928528. This pull request is rebuilt each time dev passes its smoke test after a push to `main`, and closed once staging matches dev. To hold a service back, push to `bot/promote-staging` by hand: the bot leaves the branch alone while this pull request is open. ## hilt `sha256:80076f3` → `sha256:5b6d022` What fil-forge/hilt brings over staging's pin, `a026d16..42dd070`: - feat(s3perm): add the policy vocabulary (fil-forge/hilt#58) (89d4ccf) - feat(store): add the principal store and share-locked reads (fil-forge/hilt#59) (434839e) - feat(bucketpolicy): add the bucket policy and its evaluation (fil-forge/hilt#60) (b30465e) - feat(store): add the bucket policy store with compare-and-set writes (fil-forge/hilt#61) (1ff52cb) - fix(store): bound every Postgres lock wait (fil-forge/hilt#84) (98d7a12) - feat(api): bind access keys to principals (fil-forge/hilt#64) (894624d) - feat(grant): rotate and revoke principal-bound key delegations (fil-forge/hilt#65) (e372bcd) - feat(api): add principals and principal-bound access keys (fil-forge/hilt#66) (880f780) - feat(api): add bucket policies and the principal access reads (fil-forge/hilt#67) (f366100) - feat(rpc): authorize principal-bound keys against bucket policies (fil-forge/hilt#68) (9e93a34) - feat(rpc): answer /s3/bucket/info for a principal-bound key (fil-forge/hilt#85) (b7ed334) - test(itest): cover the IAM model end to end and document it (fil-forge/hilt#69) (fa1512f) - feat(api): revoke every key's delegations before removing a tenant (fil-forge/hilt#80) (70f9380) - feat(rpc): read and write bucket policies over /s3/bucket/policy (fil-forge/hilt#89) (6a675f4) - feat(bucketpolicy): make the bucket policy an AWS bucket policy document (fil-forge/hilt#96) (42dd070)
Sets every staging pin in `terraform/envs/staging/apps/terraform.tfvars` to what `terraform/envs/dev/apps/terraform.tfvars` pins at d236fcd. Merging this is the promotion: Check and deploy applies `terraform/envs/staging/apps` on the merge commit, with no further confirmation. dev passed its smoke test on that commit in https://github.com/fil-forge/infra-central/actions/runs/38583928528. This pull request is rebuilt each time dev passes its smoke test after a push to `main`, and closed once staging matches dev. To hold a service back, push to `bot/promote-staging` by hand: the bot leaves the branch alone while this pull request is open. ## hilt `sha256:80076f3` → `sha256:5b6d022` What fil-forge/hilt brings over staging's pin, `a026d16..42dd070`: - feat(s3perm): add the policy vocabulary (fil-forge/hilt#58) (89d4ccf) - feat(store): add the principal store and share-locked reads (fil-forge/hilt#59) (434839e) - feat(bucketpolicy): add the bucket policy and its evaluation (fil-forge/hilt#60) (b30465e) - feat(store): add the bucket policy store with compare-and-set writes (fil-forge/hilt#61) (1ff52cb) - fix(store): bound every Postgres lock wait (fil-forge/hilt#84) (98d7a12) - feat(api): bind access keys to principals (fil-forge/hilt#64) (894624d) - feat(grant): rotate and revoke principal-bound key delegations (fil-forge/hilt#65) (e372bcd) - feat(api): add principals and principal-bound access keys (fil-forge/hilt#66) (880f780) - feat(api): add bucket policies and the principal access reads (fil-forge/hilt#67) (f366100) - feat(rpc): authorize principal-bound keys against bucket policies (fil-forge/hilt#68) (9e93a34) - feat(rpc): answer /s3/bucket/info for a principal-bound key (fil-forge/hilt#85) (b7ed334) - test(itest): cover the IAM model end to end and document it (fil-forge/hilt#69) (fa1512f) - feat(api): revoke every key's delegations before removing a tenant (fil-forge/hilt#80) (70f9380) - feat(rpc): read and write bucket policies over /s3/bucket/policy (fil-forge/hilt#89) (6a675f4) - feat(bucketpolicy): make the bucket policy an AWS bucket policy document (fil-forge/hilt#96) (42dd070) Co-authored-by: fil-forge-bot[bot] <318653112+fil-forge-bot[bot]@users.noreply.github.com>
Sets every prod pin in `terraform/envs/prod/apps/terraform.tfvars` to what `terraform/envs/staging/apps/terraform.tfvars` pins at 6cdeb78. Merging this is the promotion: Check and deploy applies `terraform/envs/prod/apps` on the merge commit, with no further confirmation. staging passed its smoke test on that commit in https://github.com/fil-forge/infra-central/actions/runs/38589211473. This pull request is rebuilt each time staging passes its smoke test after a push to `main`, and closed once prod matches staging. To hold a service back, push to `bot/promote-prod` by hand: the bot leaves the branch alone while this pull request is open. ## hilt `sha256:80076f3` → `sha256:5b6d022` What fil-forge/hilt brings over prod's pin, `a026d16..42dd070`: - feat(s3perm): add the policy vocabulary (fil-forge/hilt#58) (89d4ccf) - feat(store): add the principal store and share-locked reads (fil-forge/hilt#59) (434839e) - feat(bucketpolicy): add the bucket policy and its evaluation (fil-forge/hilt#60) (b30465e) - feat(store): add the bucket policy store with compare-and-set writes (fil-forge/hilt#61) (1ff52cb) - fix(store): bound every Postgres lock wait (fil-forge/hilt#84) (98d7a12) - feat(api): bind access keys to principals (fil-forge/hilt#64) (894624d) - feat(grant): rotate and revoke principal-bound key delegations (fil-forge/hilt#65) (e372bcd) - feat(api): add principals and principal-bound access keys (fil-forge/hilt#66) (880f780) - feat(api): add bucket policies and the principal access reads (fil-forge/hilt#67) (f366100) - feat(rpc): authorize principal-bound keys against bucket policies (fil-forge/hilt#68) (9e93a34) - feat(rpc): answer /s3/bucket/info for a principal-bound key (fil-forge/hilt#85) (b7ed334) - test(itest): cover the IAM model end to end and document it (fil-forge/hilt#69) (fa1512f) - feat(api): revoke every key's delegations before removing a tenant (fil-forge/hilt#80) (70f9380) - feat(rpc): read and write bucket policies over /s3/bucket/policy (fil-forge/hilt#89) (6a675f4) - feat(bucketpolicy): make the bucket policy an AWS bucket policy document (fil-forge/hilt#96) (42dd070)
Adds
pkg/bucketpolicy, the pure policy document package: validation, evaluation, and a canonical form whose lists are sets. The ETag is the CID of the document's DAG-CBOR encoding. Field names and effects follow AWS:Statement,Sid,Effect,Principal,Action,Allow,Deny.Decode,Validate,Canonical,ETagEffective(Allow minus Deny),Named,ChangedChange log
References
🤖 Generated with Claude Code